#ifndef _METARGS__
#define _METARGS__

/*
 * TODO: MAKE IT MORE READABLE :)
 * PERF: OPTIMIZE SPEED when there is many ma_long_short args
 * TODO: add `assert(optarg)` to reqired args
 * TODO: make variable names configurale
 */

#include <getopt.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>

#include <metalang99/bool.h>
#include <metalang99/choice.h>
#include <metalang99/gen.h>
#include <metalang99/ident.h>
#include <metalang99/lang.h>
#include <metalang99/list.h>
#include <metalang99/util.h>
#include <metalang99/variadics.h>

#ifdef METARGS_POSIXLY_CORRECT
#	define METARGS_POSIXLY_CORRECT_BOOL ML99_TRUE()
#else
#	define METARGS_POSIXLY_CORRECT_BOOL ML99_FALSE()
#endif

/*
 * argument types:
 * long, short and long linked with short
 */
#define ma_long(long_arg)   ML99_choice(v(long), long_arg)
#define ma_short(short_arg) ML99_choice(v(short), short_arg)
#define ma_long_short(long_arg, short_arg)                                    \
	ML99_choice(v(long_short), long_arg, short_arg)

/*
 * long option types:
 *   - required
 *   - optional
 *   - none
 */
#define ma_l_req(arg)  ML99_choice(v(l_req), arg)
#define ma_l_opt(arg)  ML99_choice(v(l_opt), arg)
#define ma_l_none(arg) ML99_choice(v(l_none), arg)

/*
 * short option types:
 *   - required
 *   - none
 * TODO: add optional
 */
#define ma_s_req(arg)  ML99_choice(v(s_req), v(arg))
#define ma_s_none(arg) ML99_choice(v(s_none), v(arg))

#define ma_l_name(name, id) v(name, id)
#define ma_l_id(id)	    ma_l_name(#id, id)

#define metaargs_arg(id)     arg_##id
#define metaargs_arg_set(id) config.metaargs_arg(id).is_set = !0
#define metaargs_arg_set_arg(id)                                              \
	metaargs_arg_set(id);                                                 \
	config.metaargs_arg(id).arg = optarg

#define metaargs_config_has_arg_l_req_IMPL(name, id)                          \
	v(struct {                                                            \
		unsigned    is_set : 1;                                       \
		const char *arg;                                              \
	} metaargs_arg(id);)
#define metaargs_config_has_arg_l_opt_IMPL(name, id)                          \
	v(struct {                                                            \
		unsigned    is_set : 1;                                       \
		const char *arg;                                              \
	} metaargs_arg(id);)
#define metaargs_config_has_arg_l_none_IMPL(name, id)                         \
	v(struct { unsigned is_set : 1; } metaargs_arg(id);)

#define metaargs_config_has_arg_s_req_IMPL(id)                                \
	v(struct {                                                            \
		unsigned    is_set : 1;                                       \
		const char *arg;                                              \
	} metaargs_arg(id);)
#define metaargs_config_has_arg_s_none_IMPL(id)                               \
	v(struct { unsigned is_set : 1; } metaargs_arg(id);)

#define metaargs_gen_config_long_IMPL(arg)                                    \
	ML99_match(v(arg), v(metaargs_config_has_arg_))
#define metaargs_gen_config_short_IMPL(arg)                                   \
	ML99_match(v(arg), v(metaargs_config_has_arg_))
#define metaargs_gen_config_long_short_IMPL(long, short)                      \
	ML99_match(v(long), v(metaargs_config_has_arg_))

#define MATCH_CONF_IMPL(x) ML99_match(x, v(metaargs_gen_config_))
#define MATCH_CONF_ARITY   1

#define metaargs_gen_config(...)                                              \
	ML99_EVAL(ML99_variadicsForEach(v(MATCH_CONF), v(__VA_ARGS__)))

#define metaargs_getopt_entry_IMPL(long, req, short)                          \
	ML99_list(v({ long, req, NULL, short }))
#define metaargs_getopt_entry_ARITY 3

#define metaargs_filter_long_l_req_IMPL(name, id, ch)                         \
	ML99_call(v(metaargs_getopt_entry), v(name, required_argument, ch))
#define metaargs_filter_long_l_opt_IMPL(name, id, ch)                         \
	ML99_call(v(metaargs_getopt_entry), v(name, optional_argument, ch))
#define metaargs_filter_long_l_none_IMPL(name, id, ch)                        \
	ML99_call(v(metaargs_getopt_entry), v(name, no_argument, ch))

#define metaargs_filter_long_long_IMPL(x)                                     \
	ML99_matchWithArgs(                                                   \
	    v(x),                                                             \
	    v(metaargs_filter_long_),                                         \
	    ML99_call(metaargs_gen_long_enum_id, ML99_choiceData(v(x))))

#define metaargs_filter_long_short_IMPL(x) ML99_nil()
#define metaargs_filter_long_long_short_IMPL(long, short)                     \
	ML99_matchWithArgs(v(long),                                           \
			   v(metaargs_filter_long_),                          \
			   ML99_charLit(ML99_choiceData(v(short))))

#define metaargs_filter_long_IMPL(x) ML99_match(x, v(metaargs_filter_long_))
#define metaargs_filter_long_ARITY   1
#define metaargs_gen_long_opts_internal(...)                                  \
	ML99_listFoldl(                                                       \
	    v(ML99_listAppend),                                               \
	    ML99_nil(),                                                       \
	    ML99_listMap(v(metaargs_filter_long), ML99_list(v(__VA_ARGS__))))

#define metaargs_gen_long_opts(...)                                           \
	ML99_LIST_EVAL_COMMA_SEP(                                             \
	    ML99_listAppend(metaargs_gen_long_opts_internal(__VA_ARGS__),     \
			    ML99_list(v({ NULL, 0, NULL, 0 }))))

#define metaargs_filter_short_s_none_IMPL(x) ML99_list(ML99_charLit(v(x)))
#define metaargs_filter_short_s_req_IMPL(x)                                   \
	ML99_listAppend(ML99_list(ML99_charLit(v(x))), ML99_list(v(':')))
#define metaargs_filter_short_short_IMPL(x)                                   \
	ML99_match(v(x), v(metaargs_filter_short_))

#define metaargs_filter_short_long_IMPL(x) ML99_nil()

#define metaargs_filter_short_long_short_IMPL(long, short)                    \
	ML99_call(v(metaargs_filter_short_short), v(short))

#define metaargs_filter_short_IMPL(x) ML99_match(x, v(metaargs_filter_short_))
#define metaargs_filter_short_ARITY   1

#define metaargs_gen_short_opts_inner(...)                                    \
	ML99_listFoldl(v(ML99_listAppend),                                    \
		       ML99_IF(METARGS_POSIXLY_CORRECT_BOOL,                  \
			       ML99_list(v('+')),                             \
			       ML99_nil()),                                   \
		       ML99_listMap(v(metaargs_filter_short),                 \
				    ML99_list(v(__VA_ARGS__))))
#define metaargs_gen_short_opts(...)                                          \
	ML99_LIST_EVAL_COMMA_SEP(                                             \
	    ML99_listAppend(metaargs_gen_short_opts_inner(__VA_ARGS__),       \
			    ML99_list(v('\0'))))

#define metaargs_gen_long_enum_id_ARITY		 2
#define metaargs_gen_long_enum_id_IMPL(name, id) v(PARSE_ARGS_LONG_##id)
#define metaargs_filter_long_enum_long_IMPL(x)                                \
	ML99_list(ML99_call(metaargs_gen_long_enum_id, ML99_choiceData(v(x))))

#define metaargs_filter_long_enum_short_IMPL(x)		       ML99_nil()
#define metaargs_filter_long_enum_long_short_IMPL(long, short) ML99_nil()
#define metaargs_filter_long_enum_IMPL(x)                                     \
	ML99_match(x, v(metaargs_filter_long_enum_))
#define metaargs_filter_long_enum_ARITY 1

#define metaargs_gen_long_enum_inner(...)                                     \
	ML99_listFoldl(v(ML99_listAppend),                                    \
		       ML99_nil(),                                            \
		       ML99_listMap(v(metaargs_filter_long_enum),             \
				    ML99_list(v(__VA_ARGS__))))
#define metaargs_gen_long_enum(...)                                           \
	ML99_LIST_EVAL_COMMA_SEP(                                             \
	    ML99_listAppend(ML99_list(v(_PARSE_ARGS_LONG__ = UCHAR_MAX + 1)), \
			    metaargs_gen_long_enum_inner(__VA_ARGS__)))

#define metaargs_gen_parser_long_case(case_id) v(case case_id:)

#define metaargs_gen_parser_s_req_IMPL(x)                                     \
	ML99_prefixedBlock(metaargs_gen_parser_long_case(ML99_CHAR_LIT(x)),   \
			   v(metaargs_arg_set_arg(x); break;))
#define metaargs_gen_parser_s_none_IMPL(x)                                    \
	ML99_prefixedBlock(metaargs_gen_parser_long_case(ML99_CHAR_LIT(x)),   \
			   v(metaargs_arg_set(x); break;))
#define metaargs_gen_parser_short_IMPL(x)                                     \
	ML99_match(v(x), v(metaargs_gen_parser_))

#define metaargs_gen_parser_l_none_IMPL(name, id, case_id)                    \
	ML99_prefixedBlock(metaargs_gen_parser_long_case(case_id),            \
			   v(metaargs_arg_set(id); break;))

#define metaargs_gen_parser_l_req_IMPL(name, id, case_id)                     \
	ML99_prefixedBlock(metaargs_gen_parser_long_case(case_id),            \
			   v(metaargs_arg_set_arg(id); break;))
#define metaargs_gen_parser_l_opt_IMPL(name, id, case_id)                     \
	ML99_prefixedBlock(metaargs_gen_parser_long_case(case_id),            \
			   v(metaargs_arg_set_arg(id); break;))

#define metaargs_gen_parser_long_IMPL(x)                                      \
	ML99_matchWithArgs(                                                   \
	    v(x),                                                             \
	    v(metaargs_gen_parser_),                                          \
	    ML99_call(metaargs_gen_long_enum_id, ML99_choiceData(v(x))))

#define metaargs_gen_parser_long_short_IMPL(long, short)                      \
	ML99_matchWithArgs(v(long),                                           \
			   v(metaargs_gen_parser_),                           \
			   ML99_charLit(ML99_choiceData(v(short))))

#define metaargs_gen_parser_ARITY   1
#define metaargs_gen_parser_IMPL(x) ML99_match(x, v(metaargs_gen_parser_))

#define metaargs_gen_parser_inner(...)                                        \
	ML99_variadicsForEach(v(metaargs_gen_parser), v(__VA_ARGS__))
#define metaargs_gen_parser(ch, ...)                                          \
	switch ((ch)) {                                                       \
		ML99_EVAL(metaargs_gen_parser_inner(__VA_ARGS__))             \
	default:                                                              \
		config.error = !0;                                            \
	}                                                                     \
	ML99_TRAILING_SEMICOLON()

#define metaargs(prog, ...)                                                   \
	typedef struct ArgsConfig_##prog {                                    \
		int	 argc;                                                \
		char   **argv;                                                \
		unsigned error : 1;                                           \
		metaargs_gen_config(__VA_ARGS__)                              \
	} ArgsConfig_##prog;                                                  \
	static inline ArgsConfig_##prog parse_args_##prog(int	argc,         \
							  char *argv[])       \
	{                                                                     \
		enum PARSE_ARGS_LONG {                                        \
			metaargs_gen_long_enum(__VA_ARGS__),                  \
		};                                                            \
		static const struct option long_opts[] = {                    \
			metaargs_gen_long_opts(__VA_ARGS__),                  \
		};                                                            \
		static const char short_opts[] = {                            \
			metaargs_gen_short_opts(__VA_ARGS__),                 \
		};                                                            \
		ArgsConfig_##prog config = { 0 };                             \
		int		  ch;                                         \
		while ((ch = getopt_long(argc,                                \
					 argv,                                \
					 short_opts,                          \
					 long_opts,                           \
					 NULL))                               \
		       != -1) {                                               \
			metaargs_gen_parser(ch, __VA_ARGS__);                 \
		}                                                             \
		int rest    = optind ? optind : 1;                            \
		config.argc = argc - rest;                                    \
		config.argv = &argv[rest];                                    \
		return config;                                                \
	}                                                                     \
	ML99_TRAILING_SEMICOLON()

/* TODO: remove this */
/* clang-format off */
/*
// usgae example
metaargs( /1* NOLINT *1/
	ma_long_short(ma_l_none(ma_l_id(directory)), ma_s_none(d)),
	ma_long_short(ma_l_none(ma_l_id(quiet)), ma_s_none(q)),
	ma_long_short(ma_l_none(ma_l_name("dry-run", dry_run)), ma_s_none(u)),
	ma_long(ma_l_none(ma_l_id(suffix))),
	ma_long_short(ma_l_opt(ma_l_id(tmpdir)), ma_s_req(p)),
	ma_short(ma_s_req(t)),
	ma_short(ma_s_none(V)),
	ma_long(ma_l_req(ma_l_id(help))),
	ma_long(ma_l_opt(ma_l_id(version)))
);
*/
/* clang-format on */

#endif
